package com.alexrnl.BetaSeriesFileManager.API.communication;

import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.logging.Logger;

import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;

import org.w3c.dom.Document;
import org.xml.sax.SAXException;

import com.alexrnl.BetaSeriesFileManager.API.BetaSeriesAPIException;
import com.alexrnl.BetaSeriesFileManager.API.Utils;
import com.alexrnl.BetaSeriesFileManager.API.dao.ErrorDAO;
import com.alexrnl.BetaSeriesFileManager.API.dao.interfaces.IErrorDAO;

/**
 * Class in charge of the communication between the application and the API. <br />
 * Allow the definition of default parameter that will be sent with each query to the host.
 * 
 * @author Alex
 */
public class QueryManager {
	private static Logger				T	= Logger.getLogger(QueryManager.class.getName());

	private final Map<String, String>	compulsoryParams;
	private String						host;
	private final IErrorDAO				errorDAO;

	/**
	 * Constructor #1.<br />
	 * Build a query manager with default parameters which will be sent along with each request.
	 * 
	 * @param host
	 *            the host server
	 * @param compulsoryParams
	 *            the parameters that should be sent with each request
	 */
	public QueryManager (final String host, final Map<String, String> compulsoryParams) {
		this.host = host;
		this.compulsoryParams = compulsoryParams;
		errorDAO = new ErrorDAO();

		if (!this.host.startsWith("http")) {
			this.host = "http://" + this.host;
			T.warning("host does not starts with 'http' adding protocol to host.");
		}
	}

	/**
	 * Constructor #2.<br />
	 * Build a query manager with <b>no</b> default parameters.
	 * 
	 * @param host
	 *            the host server
	 * @see #QueryManager(String, Map)
	 */
	public QueryManager (final String host) {
		this(host, new HashMap<String, String>());
	}

	/**
	 * Add a compulsory parameter.<br />
	 * Overwrites the previous parameter if it is already present.
	 * 
	 * @param parameter
	 *            the name of the parameter.
	 * @param value
	 *            the value of the parameter.
	 * @throws NullPointerException
	 *             if <code>parameter</code> or <code>value</code> is <code>null</code>.
	 */
	public void addParameter (final String parameter, final String value)
			throws NullPointerException {
		if (parameter == null || value == null)
			throw new NullPointerException("Cannot add a null parameter (or value) to"
					+ "the default parameters.");
		compulsoryParams.put(parameter, value);
	}

	/**
	 * Execute the request for the given page, without any parameters (apart from the compulsory
	 * parameters defined in the {@link #QueryManager(String, HashMap) constructor}).
	 * 
	 * @param page
	 *            the page to query
	 * @return the XML document returned by the API
	 * @throws BetaSeriesAPIException
	 *             the API errors that occurred during the query
	 */
	public Document execute (final String page) throws BetaSeriesAPIException {
		return execute(page, new HashMap<String, String>());
	}

	/**
	 * Execute the request for the given page with the <code>params</code> sent.
	 * @param page
	 *            the page to query.
	 * @param params
	 *            the parameters to transmit to the page.
	 * @return the XML document returned by the API.
	 * @throws BetaSeriesAPIException
	 *             the API errors that occurred during the query
	 */
	public Document execute (final String page, final Map<String, String> params)
			throws BetaSeriesAPIException {
		params.putAll(compulsoryParams);

		final String url = host + "/" + page + (page.endsWith("xml") ? "" : ".xml") + "?"
				+ formatParamForRequest(params);
		T.info("formatted url request: " + url);

		Document doc = null;
		try {
			doc = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(url);
		} catch (final SAXException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (final IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (final ParserConfigurationException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		if (Utils.hasError(doc))
			throw errorDAO.getErrors(doc);
		return doc;
	}

	/**
	 * Format the parameters for a request to the API.<br />
	 * Formatting: <code>param1=value1&amp;param2=value2&amp;<i>[...]</i>&amp;paramN=valueN</code>
	 * 
	 * @param params
	 *            the parameters.
	 * @return a formatted string with the parameters.
	 */
	private static String formatParamForRequest (final Map<String, String> params) {
		if (params == null || params.isEmpty())
			throw new IllegalArgumentException("Cannot format parameters: map is null or empty.");

		String parameters = "";
		for (final Entry<String, String> currentEntry : params.entrySet()) {
			parameters += currentEntry.getKey() + "=" + currentEntry.getValue() + "&";
		}
		if (!parameters.isEmpty() && parameters.endsWith("&"))
			return parameters.substring(0, parameters.length() - 1);
		else
			return parameters;
	}

}
